A Robotic Tea Steeper
Embedded Project By Kayla Rossi.
This system consisted of multiple elements that combined together to make a robotic tea steeper. It cumulates mechanical and electronic parts through python programming in a linux operating system on a raspberry pi 4 (RPi) to create one large embedded system. This system works using multiple servo motors to dispense loose leaf tea, as chosen by the user, into a diffuser. It then pulls the diffuser through a self-closing ramp and dips the diffuser into a mug of hot water. Meanwhile, a countdown for the tea brew starts on the piTFT screen, the diffuser is steeped by being slightly lowered and lifted in the hot water. Once the steeping time is completed, the diffuser is withdrawn from the mug and the tea is brewed.
The mechanical design of the system evolved as the idea become more refined after idea conception. As depicted in figure 1, the original sketch involved a complex design of rotational to linear motion mechanisms and two continuous motors. After some discussion during project proposal, a few idea improvements were presented for consideration. One idea was to consider integrating a way for hot water to be automatically brewed and placed into position under the diffuser. Another idea was to incorporate a way to automate dispensing tea into a diffuser thus replacing the idea of a user putting a previously filled diffuser into the brewing mechanism. Lastly, the brewing mechanism that was used in the proposal, as seen in figure 1, was suggested to utilize a standard servo motor in place of the rotational to linear motion actuated by a continuous servo. This combination of ideas sparked larger changes for the entire mechanical design.
An amount of time was spent on considering how to involve a keurig style machine to heat up hot water and use a turn-style/lazy-susan type mechanism to swivel the mug into location under the diffuser. The complications that surfaced with this idea were largely surrounding concerns of weight. The combined weight of a ceramic mug plus 8-12 ounces of water, approximately as much as 1 pound, would be a heavy load for a small geared DC or servo motor to move. A more thorough motor selection would be needed in this regard and it was felt that the general idea addition did not add any significant value to the overall project.
It was decided that a more value-added path for the project direction was to incorporate a way to dispense tea into the diffuser before it was delivered into the mug. The original idea did allow for three types of tea to be selected by user input that would have an assigned brewing time to each. The assigned brewing time would be a specified duration that the diffuser would be in the hot water, to be discussed in more detail in programming design section below. Three types of tea were chosen to be dispensed - black tea, green tea, and herbal tea. The manner in which the teas were to be dispensed is based on a revolver/hopper design and as such the dispenser is referred to as the teavolver. The final overall system design can be seen in the CAD assembly in figure 2.
The manner in which each motorized part would actuate was straightforward. A standard servo would be needed for the teavolver and pacman plate. These parts would need to rotate to a specific angle each time, making the standard servo ideal. The motor that would be responsible for reeling in the diffuser through the self-closing ramp would need to be a continuous motor. A DC motor was ruled inadequate due to very low torque, as such, a continuous servo motor was used. All of the servo motors that were used were parallax motors and datasheets can be located in the references section.
The electrical design was relatively simple due to the nature of servo motors. Three GPIO (general purpose input/output) pins were used, one for each servo. The two standard servo motors were connected to the the two GPIO pins that are associated with hardware PWM (pulse width modulation) within the RPi, pins 12 and 13. As can be seen in figure 3, each GPIO was connected to a 1 kOhm resistor to protect the RPi from any feedback current. The resistor then connected to an LED which provided a visual feedback that the GPIO pin was producing output, and the other end of the LED went to ground. In parallel with the LED was the signal input for the servo motor. Each motor was also connected to input voltage from 6V power supply (battery pack) and connected to a common ground. The common ground in the circuit comes from the RPi GPIO breakout header and is connected to the ground header on the protoboard. All equipment in the system is connected to the same common ground.
The continuous motor was connected in the exact same manner as the two standard servos, the only difference being that it was not connected to a GPIO pin that was associated with hardware PWM in the RPi. It was connected to GPIO pin 19.
All of the programming for the system was conducted in python on a linux operating system, run on the RPi. The script that provided all of the functionality of each component for the whole system was called main.py. Numerous test scripts were created to assist in determining the necessary motion of the standard and continuous servos. This was then fed back into the main.py script and implemented. Additionally, the touchscreen prompts and buttons were first tested in test scripts. The piTFT display and touchscreen used pygame, a program that assists in visual display using Python language. The test scripts for the pygame aspects insured proper functionality of the pygame features and display before integrating into the main.py script.
There are two menus in the system that require user selection. The first menu presents tea types, i.e. black tea, green tea, and herbal tea, and the second menu lists tea strengths, i.e. weak, normal, strong. The tea strengths determine the duration of steep time per tea type as can be seen in Table 1. The final screen on the piTFT shows the countdown of time in minutes and seconds based on the prior selections of tea type and strength. In addition to these various selections, each screen has an option to quit the program by utilizing a programmed quit button displayed on the piTFT screen for the first two menus, or the indicated piTFT physical button while the countdown is running. The physical button was a necessity on the countdown screen due to the usage of time.sleep to control the continuous motor in that particular section of code. When using time.sleep, the touchscreen is unavailable to recognize or read touches during 4 out of the 5 seconds between the usage of time.sleep, thus rendering the touchscreen unusable during that time.
Multiple functions were needed in the code to keep it condensed and to keep the motor actuations concise for each call. The following were functions created for the specified motor actions:
The script was run with one main while loop. The loop had two if statements that represented the two menus on the piTFT. They not only consisted of the display for each menu and associated touch recognition, but they also set the teavolver into the correct position based on tea selection and fed the proper steep time into the countdown function based on user selection.
At the start of system assembly, all parts were hot glued to the base plane or to each other. The thought was that if any parts interfere, they would not be permanently mounted and thus would be easier to be relocated and realigned within the system. A few parts in the original design were not designed with alignment references and this lead to interference when parts were starting to be assembled, especially when attached to motors. When some other parts needed to be redesigned, alignment references were added to avoid some of the alignment problems.
One of the first issues that was noticed during testing was that all of the cylinders in the teavolver were unable to be reached by the servo motor. This took a while to realize that the servo only had a travel distance of 180 degrees instead of 360 degrees. The part was designed with the intent that the motor could reach 180 degrees one direction and 180 degrees the opposite direction. After reading through the Parallax data sheets, it was understood that that thought was incorrect and the teavolver had to be redesigned to include all three tea holes within 180 degrees of range (figure 4). The two outer holes were tangent to the midplane at 180 degrees, to ensure the full diameter of the circle would be reachable. Additionally, a small recessed hole was added to the teavolver and the pacman plate on the centerline of each to help center the parts relative to the respective motors. Lastly, during the redesign a deduction in part size for the teavolver, pacman plate, and the rectangular base plate was introduced. The deduction in size was a result of various cascading geometric factors between the parts.
The self-closing slide was not fully redesigned to the extent that it was reprinted, but it did have significant modification using cardboard. A more narrow ramp was created internal to the slide that allowed for more of a cradle for the diffuser, making it more stable in position. Additionally, two flat layers of cardboard were added to the base at the exit of the slide to create a tighter exit location. This encouraged a more secure close on the diffuser. Another addition to encourage a secure closure on the diffuser after tea was dispensed, was the addition of magnets. Two very strong magnets were glued to the outside of the diffuser on either side of the shell. This addition does mean that the tea would not necessarily be drinkable due to the glue in hot water. As such, this is more of a prototyped system overall due to this addition. It would be suggested that either a diffuser that already is constructed using magnets is used in the system, or a custom diffuser is made for the system that utilizes magnets for closure. In addition to the magnets on the diffuser, a weak flexible magnet was used at the starting point of the slide to encourage the diffuser to sit in a more upright, right-angled position for tea dispensing. Because the magnet in this location was weak, it easily released the magnet on the diffuser, allowing for a guaranteed closure when the diffuser was pulled through the slide.
As stated in the program design section, in an effort to test each aspect before converging on one large main script, various smaller test scripts were written specifically for various components in the system. Once these were proven to work, they were implemented into main.py. The programming for the standard and continuous servos took some time to understand and as such, individual test codes were written. When using the continuous servo, it was only vital for the system to use a clockwise and counterclockwise action. These actions were taken from the Parallax data sheets on pages 5 and 6, shown under Communication Protocol. To gain a clockwise rotation, the pulse width of 1.3 ms is needed and to gain a counterclockwise rotation, a pulse width of 1.7 ms is needed. The respective duty cycles were calculated from these numbers, 6.5% and 8.5% for clockwise and counterclockwise, and were hardcoded into the main.py script. The second variable needed for actuation of the continuous motors was time. The way in which this was implemented in the code was to set the duty cycle for the respective direction that was desired and follow it with a time.sleep call of a specified number of seconds, then change the duty cycle to 0% to stop the motor action.
The standard servo had some more complications when determining how to articulate the desired motor angles. Looking at the Parallax data sheets, on page 1 it is listed that the communication band from 0 to 180 degrees uses the pulse widths of 0.75 ms to 2.25 ms. An equation was developed to associate these limits in duty cycle percentage with an input angle variable. This allowed a streamlined method to dictate angle positional output of the motor through a duty cycle input. The equation that was developed was derived to be: dutycycle = ((((top of range - bottom of range) * angle) / 180) + bottom of range), which resulted in the following when applying the specific requirements for the Parallax motor: dutycycle = int((((7.5 * angle) / 180) + 3.75) * 10000). The 10000 factor was needed in order to scale the duty cycle to be in the proper input range. Hardware PWM was used for both standard servo motors, and as such, the duty cycle is multiplied by 1,000,000 as per specifications. This equation was incredibly useful in the script for easy motor actuation and had successful output.
There was overall success with the final system as demonstrated and the system worked as intended, meeting the majority of goals originally set forth. The only goals that were not reached where those of implementing an emergency stop feature that would stop the countdown when pressed and lift the diffuser out of the mug. The user could then press resume that would continue the countdown and place the diffuser back into the mug. Additionally, it was originally stated that the piTFT would alert when the brewing was complete. In the current design, the piTFT returns to the linux command line when complete. It was not thought to be value added to add in an alert screen when the brewing was complete since there is a visual indication that brewing is complete with the diffuser lifting out of the mug upon completion.
The panic stop and resume button did not end up working due to the steeping action of the continuous motor. This motor was programmed systematically using time.sleep to control the duration of time the motor was actuated. The piTFT did not recognize any touches on the screen when the motor was performing the functions that went through the steeping action of lower and raising the diffuser slightly in the mug during the steeping time. The panic button was considered a lower priority item than the implementation of the steeping motor action which more readily assisted in the process of tea brewing.
Some aspects that can be considered for future work include more refined prototyped parts such as the self-closing ramp and the teavolver. It was incredibly tedious and difficult to refill the teavolver once the tea had been dispensed, especially the green tea cylinder. The green tea cylinder sits at 60 degrees with respect to the motor positions. Due to the motor position and the design of the motor mourning points, there is a gap over the green tea cylinder that is far too small to reasonably refill by hand or by small paper funnel; The refill has to be done a few leaves at a time. This could be solved by redesigning the teavolver to have a standoff connected to the motor instead of the motor being directly attached to the top of the teavolver. Additionally, it would help if the motor mount for the teavolver had some holes in it that would allow one to look down through that area of the system and see the parts below it.
The self-closing ramp also needed the addition of cardboard inserts in order for the diffuser to properly nest inside the mechanism and allow the diffuser to close. Due to the inability to reasonably prototype in real time, the cardboard additions were the best solution given the circumstances and as such the whole part should be redesigned with the inclusion of the cardboard geometries.
kmr262@cornell.edu
Project Contribution: All aspects in totality.
# Kayla Rossi -kmr262 main.py # System run code for ECE5725 Final Project - RoboTEAc Steeper # import RPi.GPIO as GPIO import time import os import sys import subprocess import pigpio import pygame from pygame.locals import* os.putenv('SDL_VIDEODRIVER', 'fbcon') os.putenv('SDL_FBDEV', '/dev/fb0') os.putenv('SDL_MOUSEDRV', 'TSLIB') os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen') # pygame parameters pygame.init() screen = pygame.display.set_mode((320,240)) white = [255, 255, 255] black = [0, 0, 0] red = [255, 0, 0] green = [0, 255, 0] grey = [50, 50, 50] my_font = pygame.font.Font(None, 40) big_font = pygame.font.Font(None, 100) countquit = 1 ##### GPIO set up ##### GPIO.setmode(GPIO.BCM) GPIO.setup(19, GPIO.OUT) cm = GPIO.PWM(19, 50) #initalize continous motor cm.start(0) #give continuous motor 0 duty cycle freqms = 20 #freq in milliseconds for 50 Hz used for calc dutycycle ccw = (1.3/20) * 100 #duty cycle equvialent countclockwise viewed from back of motor cw = (1.7/20) * 100 #duty cycle equivalent clockwise viewed from back of motor # connect to pi gpio deamon for hardware PWM used for stnd servos pi_hw = pigpio.pi() # Standard servos pulse width from 0.75 ms to 2.25 ms - 0 to 180 deig # inital pos 0deg - 0.75 ms/20 ms = 3.75% # max pos 180deg - 2.25 ms/20 ms = 11.25% # neutral pos 90 deg - 1.5 ms/20 ms = 7.5% # duty cycle between 3.75 and 11.25% for standard servo freq = 50 pacman = 12 #GPIO pin for motor actuating pacman plate teavolver = 13 #GPIO pin for motor actuating teavolver # percentage multipled by 1M because hardware PWM makes 1M steps when fully on dczero = int(.0375 * 1000000) dcmax = int(.1125 * 1000000) dcmid = int(.075 * 1000000) # call back routine and function for physical quit button on piTFT GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP) def GPIO27_cb(channel): global button_run global countquit print('Physical quit pressed') countquit = 0 button_run = False GPIO.add_event_detect(27, GPIO.FALLING, callback=GPIO27_cb, bouncetime=300) ##### Function definitions ##### def countdown(t): # function input variable t - units of seconds # output - actuate motor for tea dispense into diffuser # pull diffuser through self-closing ramp and reel into mug of tea # countdown on piTFT starts based on tea and strength previously selected # steep tea for 2 seconds clockwise and 2 seconds counterclockwise # countdown updates on piTFT screen every 5 seconds # countdown ends, diffuser is pulled out of mug end of function setMotorP() time.sleep(2) HomingT(0) time.sleep(1) PullTea(ccw,10) #pull through slide PullTea(cw, 10) #drop into mug global countquit toggle = 0 chcolor = 1 while (t >= 0 and countquit): screen.fill(grey) mins, secs = divmod(t,60) timer = '{:02d}:{:02d}'.format(mins, secs) print(timer, end="\r") textsurf = big_font.render(str(timer), True, white) recttime = textsurf.get_rect(center = (160,120)) screen.blit(textsurf, recttime) time.sleep(1) if(toggle): print('stop') else: t -= 5 for my_wtext, wtext_pos in wait.items(): wtext_surface = my_font.render(my_wtext, True, white) wrect = wtext_surface.get_rect(center=wtext_pos) screen.blit(wtext_surface, wrect) pygame.display.flip() PullTea(cw,2) PullTea(ccw,2) PullTea(ccw,5) print('quitting') def setMotorP(): # dutycyle multiplied by 10000 because dc var is in units (%), need to # convert back to decimal percentage and multiply by 1M for hardware PWM # multiply dc by 0.01 and then by 1000000 equates to one multiply by 10000 # # Start on 0 deg and rotate 180 for tea drop then return to 0 deg once # tea has been dispensed angleP = 180 print('home') dc = int((((7.5*angleP)/180) + 3.75) * 10000) pi_hw.hardware_PWM(pacman, freq, dczero) print('tea drop') time.sleep(5) pi_hw.hardware_PWM(pacman, freq, dc) print('home') time.sleep(2) pi_hw.hardware_PWM(pacman, 0, 0) def setMotorT(angleT): # Start on 0 degrees irrelavant of tea choice and depending on choice # rotate to input angleT variable (an angle between 0 and 180) dc = int((((7.5*angleT)/180) + 3.75) * 10000) pi_hw.hardware_PWM(teavolver, freq, dc) print('tea motor angle') time.sleep(2) pi_hw.hardware_PWM(teavolver, 0, 0) def PullTea(direction,secs): # controls actuation of continuous motor # function input - a direction (cw or ccw variable as defined above) and duration of time in seconds cm.ChangeDutyCycle(direction) time.sleep(secs) cm.ChangeDutyCycle(0) def HomingP(): # Insure pacman motor is in home position # if motor isn't at angle 180, set to angle 180 if pi_hw.hardware_PWM(pacman, freq, 112500) == False: pi_hw.hardware_PWM(pacman, freq, 112500) def HomingT(angleT): # Insure teavolver is in home position # if motor isn't at angle 0, set to angle 0 dchome = int((((7.5*angleT)/180) + 3.75) * 10000) if pi_hw.hardware_PWM(teavolver, freq, dchome) == False: pi_hw.hardware_PWM(teavolver, freq, dchome) # Dictionaries ## dispense = {'quit':(280,200), 'Green Tea':(80,40), 'Black Tea':(80, 120), 'Herbal Tea':(80,200)} brew = {'back':(280, 200), 'Weak':(80,40), 'Normal':(80, 120), 'Strong':(80, 200)} wait = {'quit-->':(280, 215)} teatype = {'Green Tea':(60), 'Normal':(1), 'Weak':(.75), 'Strong':(1.5), 'Black Tea':(180),'Herbal Tea':(180)} screen.fill(grey) button_run = True timeout = 30 starttime = time.time() m1 = 1 m2 = 0 while button_run: time.sleep(0.2) screen.fill(grey) #time bail now = time.time() elaptime = now - starttime if elaptime > timeout: button_run = False #menu 1 - tea type selection displayed on piTFT if (m1==1): HomingP() HomingT(0) # dispense menu rect buttons dispense_rect = {} for my_text, text_pos in dispense.items(): text_surface = my_font.render(my_text, True, white) rect = text_surface.get_rect(center=text_pos) screen.blit(text_surface, rect) dispense_rect[my_text] = rect pygame.display.flip() #look for touch and determine associated tea selection for event in pygame.event.get(): if(event.type is MOUSEBUTTONDOWN): pos=pygame.mouse.get_pos() elif(event.type is MOUSEBUTTONUP): pos = pygame.mouse.get_pos() x,y = pos print(pos) for (my_text, rect) in dispense_rect.items(): if(rect.collidepoint(pos)): if(my_text == str('quit')): print('quit pressed') m2 = 0 m1 = 0 button_run = False else: print(my_text + ' selected') teatime = teatype[(my_text)] ##### rotate teavolver into position based on selection if (my_text == 'Green Tea'): time.sleep(1) setMotorT(75) # need to go slightly past 60 in order for tea to pour in diffuser cleanly print('in position') elif (my_text == 'Black Tea'): time.sleep(1) setMotorT(180) print('in position') elif (my_text == 'Herbal Tea'): time.sleep(1) setMotorT(0) print('in position') m2 = 1 m1 = 0 #menu 2 - tea strength selection displayed on piTFT if (m2==1): brew_rect = {} for my_btext, btext_pos in brew.items(): btext_surface = my_font.render(my_btext, True, white) brect = btext_surface.get_rect(center=btext_pos) screen.blit(btext_surface, brect) brew_rect[my_btext] = brect pygame.display.flip() for event in pygame.event.get(): if(event.type is MOUSEBUTTONDOWN): pos=pygame.mouse.get_pos() elif(event.type is MOUSEBUTTONUP): pos = pygame.mouse.get_pos() x,y = pos print(pos) for (my_btext, brect) in brew_rect.items(): if(brect.collidepoint(pos)): if(my_btext == str('back')): print('back pressed') #returns back to menu 1 to change tea type selection m1 = 1 m2 = 0 else: print(my_btext + ' selected') teatime = int(teatime * teatype[(my_btext)]) time.sleep(1) countdown(teatime) button_run = False print('outta here') pi_hw.stop() cm.stop() time.sleep(0.1) GPIO.cleanup()